<?php

namespace Luracast\Restler {

	/**
	 * Class that implements spl_autoload facilities and multiple
	 * conventions support.
	 * Supports composer libraries and 100% PSR-0 compliant.
	 * In addition we enable namespace prefixing and class aliases.
	 *
	 * @category Framework
	 * @package Restler
	 * @subpackage helper
	 * @author Nick Lombard <github@jigsoft.co.za>
	 * @copyright 2012 Luracast
	 * @version 3.0.0rc5
	 */
	class AutoLoader {
		protected static $instance, // the singleton instance reference
$perfectLoaders, // used to keep the ideal list of loaders
$rogueLoaders = array (), // other auto loaders now unregistered
$classMap = array (), // the class to include file mapping
$aliases = array ( // aliases and prefixes instead of null list aliases
				'Luracast\\Restler' => null,
				'Luracast\\Restler\\Format' => null,
				'Luracast\\Restler\\Data' => null,
				'Luracast\\Restler\\Filter' => null 
		);
		
		/**
		 * Singleton instance facility.
		 *
		 * @static
		 *
		 * @return AutoLoader the current instance or new instance if none exists.
		 */
		public static function instance() {
			static::$instance = static::$instance ?  : new static ();
			return static::thereCanBeOnlyOne ();
		}
		
		/**
		 * Helper function to add a path to the include path.
		 * AutoLoader uses the include path to discover classes.
		 *
		 * @static
		 *
		 * @param $path string
		 *        	absolute or relative path.
		 *        	
		 * @return bool false if the path cannot be resolved
		 *         or the resolved absolute path.
		 */
		public static function addPath($path) {
			if (false === $path = stream_resolve_include_path ( $path ))
				return false;
			else
				set_include_path ( $path . PATH_SEPARATOR . get_include_path () );
			return $path;
		}
		
		/**
		 * Other autoLoaders interfere and cause duplicate class loading.
		 * AutoLoader is capable enough to handle all standards so no need
		 * for others stumbling about.
		 *
		 * @return callable the one true auto loader.
		 */
		public static function thereCanBeOnlyOne() {
			if (static::$perfectLoaders === spl_autoload_functions ())
				return static::$instance;
			
			if (false !== $loaders = spl_autoload_functions ())
				if (0 < $count = count ( $loaders ))
					for($i = 0, static::$rogueLoaders += $loaders; $i < $count && false != ($loader = $loaders [$i]); $i ++)
						if ($loader !== static::$perfectLoaders [0])
							spl_autoload_unregister ( $loader );
			
			return static::$instance;
		}
		
		/**
		 * Seen this before cache handler.
		 * Facilitates both lookup and persist operations as well as convenience,
		 * load complete map functionality. The key can only be given a non falsy
		 * value once, this will be truthy for life.
		 *
		 * @param $key mixed
		 *        	class name considered or a collection of
		 *        	classMap entries
		 * @param $value mixed
		 *        	optional not required when doing a query on
		 *        	key. Default is false we haven't seen this
		 *        	class. Most of the time it will be the filename
		 *        	for include and is set to true if we are unable
		 *        	to load this class iow true == it does not exist.
		 *        	value may also be a callable auto loader function.
		 *        	
		 * @return mixed The known value for the key or false if key has no value
		 */
		public static function seen($key, $value = false) {
			if (is_array ( $key )) {
				static::$classMap = $key + static::$classMap;
				return false;
			}
			
			if (empty ( static::$classMap [$key] ))
				static::$classMap [$key] = $value;
			
			if (is_string ( $alias = static::$classMap [$key] ))
				if (isset ( static::$classMap [$alias] ))
					return static::$classMap [$alias];
			
			return static::$classMap [$key];
		}
		
		/**
		 * Protected constructor to enforce singleton pattern.
		 * Populate a default include path.
		 * All possible includes cant possibly be catered for and if you
		 * require another path then simply add it calling set_include_path.
		 */
		protected function __construct() {
			static::$perfectLoaders = array (
					$this 
			);
			
			if (false === static::seen ( '__include_path' )) {
				
				$paths = explode ( PATH_SEPARATOR, get_include_path () );
				$slash = DIRECTORY_SEPARATOR;
				$dir = dirname ( __DIR__ );
				$source_dir = dirname ( $dir );
				$dir = dirname ( $source_dir );
				
				foreach ( array (
						array (
								$source_dir 
						),
						array (
								$dir,
								'..',
								'..',
								'composer' 
						),
						array (
								$dir,
								'vendor',
								'composer' 
						),
						array (
								$dir,
								'..',
								'..',
								'..',
								'php' 
						),
						array (
								$dir,
								'vendor',
								'php' 
						) 
				) as $includePath )
					if (false !== $path = stream_resolve_include_path ( implode ( $slash, $includePath ) ))
						if ('composer' == end ( $includePath ) && false !== $classmapPath = stream_resolve_include_path ( "$path{$slash}autoload_classmap.php" )) {
							static::seen ( static::loadFile ( $classmapPath ) );
							$paths = array_merge ( $paths, array_values ( static::loadFile ( "$path{$slash}autoload_namespaces.php" ) ) );
						} else
							$paths [] = $path;
				
				$paths = array_filter ( array_map ( function ($path) {
					if (false == $realPath = @realpath ( $path ))
						return null;
					return $realPath . DIRECTORY_SEPARATOR;
				}, $paths ) );
				natsort ( $paths );
				static::seen ( '__include_path', implode ( PATH_SEPARATOR, array_unique ( $paths ) ) );
			}
			
			set_include_path ( static::seen ( '__include_path' ) );
		}
		
		/**
		 * Attempt to include the path location.
		 * Called from a static context which will not expose the AutoLoader
		 * instance itself.
		 *
		 * @param $path string
		 *        	location of php file on the include path
		 *        	
		 * @return bool|mixed returns reference obtained from the include or false
		 */
		private static function loadFile($path) {
			return\Luracast_Restler_autoloaderInclude ( $path );
		}
		
		/**
		 * Attempt to load class with namespace prefixes.
		 *
		 * @param $className string
		 *        	class name
		 *        	
		 * @return bool|mixed reference to discovered include or false
		 */
		private function loadPrefixes($className) {
			$currentClass = $className;
			if (false !== $pos = strrpos ( $className, '\\' ))
				$className = substr ( $className, $pos );
			else
				$className = "\\$className";
			
			for($i = 0, $file = false, $count = count ( static::$aliases ), $prefixes = array_keys ( static::$aliases ); $i < $count && false === $file && false === $file = $this->discover ( $variant = $prefixes [$i ++] . $className, $currentClass ); $file = $this->loadAliases ( $variant ))
				;
			
			return $file;
		}
		
		/**
		 * Attempt to load configured aliases based on namespace part of class name.
		 *
		 * @param $className string
		 *        	fully qualified class name.
		 *        	
		 * @return bool|mixed reference to discovered include or false
		 */
		private function loadAliases($className) {
			$file = false;
			if (preg_match ( '/(.+)(\\\\\w+$)/U', $className, $parts ))
				for($i = 0, $aliases = isset ( static::$aliases [$parts [1]] ) ? static::$aliases [$parts [1]] : array (), $count = count ( $aliases ); $i < $count && false === $file; $file = $this->discover ( "{$aliases[$i++]}$parts[2]", $className ))
					;
			
			return $file;
		}
		
		/**
		 * Load from rogueLoaders as last resort.
		 * It may happen that a custom auto loader may load classes in a unique way,
		 * these classes cannot be seen otherwise nor should we attempt to cover every
		 * possible deviation. If we still can't find a class, as a last resort, we will
		 * run through the list of rogue loaders and verify if we succeeded.
		 *
		 * @param $className string
		 *        	className that can't be found
		 * @param null $loader
		 *        	callable loader optional when the loader is known
		 *        	
		 * @return bool false unless className now exists
		 */
		private function loadLastResort($className, $loader = null) {
			$loaders = array_unique ( static::$rogueLoaders );
			if (isset ( $loader )) {
				if (false === array_search ( $loader, $loaders ))
					static::$rogueLoaders [] = $loader;
				return $this->loadThisLoader ( $className, $loader );
			}
			foreach ( $loaders as $loader )
				if (false !== $file = $this->loadThisLoader ( $className, $loader ))
					return $file;
			
			return false;
		}
		
		/**
		 * Helper for loadLastResort.
		 * Use loader with $className and see if className exists.
		 *
		 * @param $className string
		 *        	name of a class to load
		 * @param $loader callable
		 *        	autoLoader method
		 *        	
		 * @return bool false unless className exists
		 */
		private function loadThisLoader($className, $loader) {
			if (is_callable ( $loader ) && false !== $file = $loader ( $className ) && $this->exists ( $className, $loader ))
				return $file;
			return false;
		}
		
		/**
		 * Create an alias for class.
		 *
		 * @param $className string
		 *        	the name of the alias class
		 * @param $currentClass string
		 *        	the current class this alias references
		 */
		private function alias($className, $currentClass) {
			if ($className != $currentClass && false !== strpos ( $className, $currentClass ))
				if (! class_exists ( $currentClass, false ) && class_alias ( $className, $currentClass ))
					static::seen ( $currentClass, $className );
		}
		
		/**
		 * Discovery process.
		 *
		 * @param $className string
		 *        	class name to discover
		 * @param $currentClass string
		 *        	optional name of current class when
		 *        	looking up an alias
		 *        	
		 * @return bool|mixed resolved include reference or false
		 */
		private function discover($className, $currentClass = null) {
			$currentClass = $currentClass ?  : $className;
			
			/**
			 * The short version we've done this before and found it in cache
			 */
			if (false !== $file = static::seen ( $className )) {
				if (! $this->exists ( $className ))
					if (is_callable ( $file ))
						$file = $this->loadLastResort ( $className, $file );
					elseif ($file = stream_resolve_include_path ( $file ))
						$file = static::loadFile ( $file );
				
				$this->alias ( $className, $currentClass );
				return $file;
			}
			
			/**
			 * We did not find it in cache, lets look for it shall we
			 */
			
			/**
			 * replace \ with / and _ in CLASS NAME with / = PSR-0 in 3 lines
			 */
			$file = preg_replace ( "/\\\|_(?=\w+$)/", DIRECTORY_SEPARATOR, $className );
			if (false === $file = stream_resolve_include_path ( "$file.php" ))
				return false;
			
			/**
			 * have we loaded this file before could this be an alias
			 */
			if (in_array ( $file, get_included_files () )) {
				if (false !== $sameFile = array_search ( $file, static::$classMap ))
					if (! $this->exists ( $className, $file ))
						if (false !== strpos ( $sameFile, $className ))
							$this->alias ( $sameFile, $className );
				
				return $file;
			}
			
			$state = array_merge ( get_declared_classes (), get_declared_interfaces () );
			
			if (false !== $result = static::loadFile ( $file )) {
				
				if ($this->exists ( $className, $file ))
					$this->alias ( $className, $currentClass );
				elseif (false != $diff = array_diff ( array_merge ( get_declared_classes (), get_declared_interfaces () ), $state ))
					foreach ( $diff as $autoLoaded )
						if ($this->exists ( $autoLoaded, $file ))
							if (false !== strpos ( $autoLoaded, $className ))
								$this->alias ( $autoLoaded, $className );
				
				if (! $this->exists ( $currentClass ))
					$result = false;
			}
			
			return $result;
		}
		
		/**
		 * Checks whether supplied string exists in a loaded class or interface.
		 * As a convenience the supplied $mapping can be the value for seen.
		 *
		 * @param $className string
		 *        	The class or interface to verify
		 * @param $mapping string
		 *        	(optional) value for map/seen if found to exist
		 *        	
		 * @return bool whether the class/interface exists without calling auto loader
		 */
		private function exists($className, $mapping = null) {
			if (class_exists ( $className, false ) || interface_exists ( $className, false ))
				if (isset ( $mapping ))
					return static::seen ( $className, $mapping );
				else
					return true;
			return false;
		}
		
		/**
		 * Auto loader callback through __invoke object as function.
		 *
		 * @param $className string
		 *        	class/interface name to auto load
		 *        	
		 * @return mixed|null the reference from the include or null
		 */
		public function __invoke($className) {
			if (empty ( $className ))
				return false;
			
			if (false !== $includeReference = $this->discover ( $className ))
				return $includeReference;
			
			static::thereCanBeOnlyOne ();
			
			if (false !== $includeReference = $this->loadAliases ( $className ))
				return $includeReference;
			
			if (false !== $includeReference = $this->loadPrefixes ( $className ))
				return $includeReference;
			
			if (false !== $includeReference = $this->loadLastResort ( $className ))
				return $includeReference;
			
			static::seen ( $className, true );
			return null;
		}
	}
}

namespace {

	/**
	 * Include function in the root namespace to include files optimized
	 * for the global context.
	 *
	 * @param $path string
	 *        	path of php file to include into the global context.
	 *        	
	 * @return mixed|bool false if the file could not be included.
	 */
	function Luracast_Restler_autoloaderInclude($path) {
		return include $path;
	}
}

